package com.lsa.helpers;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.lang.StringUtils;

public class SingleLinkedList<T> {
	private ListNode<T> head;
	private ListNode<T> tail;
	
	public ListNode<T> add(T data) {
		ListNode<T> newNode;
		if (head == null) {
			newNode  = new ListNode<T>(data);
			head = newNode;
			tail = head;
		}
		else {
			newNode = new ListNode<T>(data);
			tail.next = newNode;
			tail = newNode;
		}
		return newNode;
	}
	
	public ListNode<T> getHead() {
		return head;
	}
	
	public ListNode<T> getTail() {
		return tail;
	}

	public void remove(ListNode<T> node) {
		ListNode<T> currNode = head;
		ListNode<T> prevNode = null;
		while(currNode != null) {
			if (currNode == node){
				remove(prevNode, currNode);
				break;
			}
			prevNode = currNode;
			currNode = currNode.next;
		}
	}

	public void remove(ListNode<T> prevNode, ListNode<T> currNode) {
		if (currNode == head) {
			head = head.next;
		}
		else {
			prevNode.next = currNode.next;
		}
		
		if (currNode == tail) {
			tail = prevNode;
		}
	}
	
	public Collection<T> toCollection() {
		ArrayList<T> coll = new ArrayList<T>();
		ListNode<T> currNode = head;
		while(currNode != null) {
			coll.add(currNode.data);
			currNode = currNode.next;
		}
		return coll;
	}
	
	public boolean equals(Object o) {
		if (o == this)
			return true;
		if (!(o instanceof SingleLinkedList<?>))
			return false;

		SingleLinkedList<?> other = (SingleLinkedList<?>) o;
		ListNode<T> le1 = head;
		ListNode<?> le2 = other.getHead();
		
		while (le1 != null && le2 != null) {
			T o1 = le1.data;
			Object o2 = le2.data;
			if (!(o1 == null ? o2 == null : o1.equals(o2)))
				return false;
			
			le1 = le1.next;
			le2 = le2.next;
		}

		return le1 == null && le2 == null;
	}

	public int hashCode() {
		int hashCode = 1;
		ListNode<T> le1 = head;
		while (le1 != null) {
			T obj = le1.data;
			hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode());
			le1 = le1.next;
		}
		return hashCode;
	}
	
	@Override
	public String toString() {
		return StringUtils.join(toCollection(), " ");
	}

	public static class ListNode<T> {
		
		public ListNode(T data) {
			this.data = data;
		}
		
		public T data;
		public ListNode<T> next;
		
		@Override
		public String toString() {
			return data != null? data.toString() : "";
		}
	}
}